using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using System.Diagnostics;
using Microsoft.Xna.Framework;

namespace XNASubdivision
{
    partial class CatmullClarkSubdivider_GS_Imitation
    {
        QuadMesh m_quadMesh0;
        DXTriangleMesh m_triangleMesh;
        GraphicsDevice m_device;
        int m_subdivLevel;


        Vector3[] g_vertBuffer;
        uint[] g_vertValenceBuffer;
        Vector2[] g_texUvBuffer;	// put into separate Buffer, so if a game developer do not need for example normals, it is easy for him 
        Vector3[] g_normalBuffer;
        Int4[] g_quadBuffer;
        Int4[] g_quadTextureIndBuffer;
        Int4[] g_quadNormalIndBuffer;
        Int4[] g_quadAdjacencyBuffer;
        int[] g_quadAdjacentVerticesBuffer;    // every quad is represented in 5 int * 4 vertex = 20 int   (at valence = 5, there is 5 index needed to store)

        List<VertexPositionNormalTexture> m_finalVertices;
        List<uint> m_finalIndices;

        internal DXTriangleMesh GetTriangleMesh(GraphicsDevice p_device, QuadMesh p_quadMesh0, int p_subdivLevel)
        {
            Debug.Assert(m_subdivLevel <= 2, "Subdiv level > 2 is not implemented yet.");

            m_device = p_device;
            m_quadMesh0 = p_quadMesh0;
            m_subdivLevel = p_subdivLevel;

            m_triangleMesh = null;
            PrepareQuadData();
            for (int i = 0; i < g_quadBuffer.Length; i++)
            //for (int i = 0; i < 2; i++)
            {
                if (m_subdivLevel == 0)
                    ProcessQuadLevel0(i);
                else if (m_subdivLevel == 1)
                    ProcessQuadLevel1(i);
                else if (m_subdivLevel == 2)
                    ProcessQuadLevel2(i);
            }
            FinalizeTriangleMesh();

            return m_triangleMesh;
        }

    
        
        private void PrepareQuadData()
        {
            int nQuads = m_quadMesh0.Quads.Length;
            int nVerts = m_quadMesh0.Vertices.Length;
            int nNormals = m_quadMesh0.Normals.Length;
            int nTexUVs = m_quadMesh0.TextureUVs.Length;


            int nValenceLess3 = 0;
            int nValence3 = 0;
            int nValence4 = 0;
            int nValence5 = 0;
            int nValenceGreater5 = 0;
            g_vertBuffer = new Vector3[nVerts];
            g_vertValenceBuffer = new uint[nVerts];
            for (int i = 0; i < nVerts; i++)
            {
                g_vertBuffer[i] = m_quadMesh0.Vertices[i];
                g_vertValenceBuffer[i] = FindValence(i);
                if (g_vertValenceBuffer[i] <= 2)
                    nValenceLess3++;
                else if (g_vertValenceBuffer[i] == 3)
                    nValence3++;
                else if (g_vertValenceBuffer[i] == 4)
                    nValence4++;
                else if (g_vertValenceBuffer[i] == 5)
                    nValence5++;
                else
                    nValenceGreater5++;
            }
            if (nValenceLess3 > 0 || nValenceGreater5 > 0)
            {
                Trace.WriteLine("Warning! (nValenceLess3 > 0 || nValenceGreater5 > 0) is not handled correctly");
                //Debug.Assert(false);   // it is not handled by the program correctly
            }

            g_normalBuffer = (Vector3[])m_quadMesh0.Normals.Clone();
            g_texUvBuffer = (Vector2[])m_quadMesh0.TextureUVs.Clone();




            // 2. generate the quad data

            
            //g_quadAdjacencyBuffer = new Int4[nQuads];
            //for (int i = 0; i < m_quadMesh0.Quads.Length; i++)
            //{
            //    int vert0 = m_quadMesh0.Quads[i].vertInd0;
            //    int vert1 = m_quadMesh0.Quads[i].vertInd1;
            //    int vert2 = m_quadMesh0.Quads[i].vertInd2;
            //    int vert3 = m_quadMesh0.Quads[i].vertInd3;

            //    g_quadBuffer[i] = new Int4(vert0, vert1, vert2, vert3);

            //    g_quadAdjacencyBuffer[i].x = FindQuadIndex(vert0, vert1, i);
            //    g_quadAdjacencyBuffer[i].y = FindQuadIndex(vert1, vert2, i);
            //    g_quadAdjacencyBuffer[i].z = FindQuadIndex(vert2, vert3, i);
            //    g_quadAdjacencyBuffer[i].w = FindQuadIndex(vert3, vert0, i);
            //}

            g_quadBuffer = new Int4[nQuads];
            for (int i = 0; i < m_quadMesh0.Quads.Length; i++)
            {
                int vert1 = m_quadMesh0.Quads[i].vertInd0;
                int vert2 = m_quadMesh0.Quads[i].vertInd1;
                int vert3 = m_quadMesh0.Quads[i].vertInd2;
                int vert4 = m_quadMesh0.Quads[i].vertInd3;

                g_quadBuffer[i] = new Int4(vert1, vert2, vert4, vert3);
            }


            g_quadTextureIndBuffer = (Int4[])m_quadMesh0.QuadTextureInd.Clone();
            for (int i = 0; i < g_quadTextureIndBuffer.Length; i++)
            {
                int tempValue = g_quadTextureIndBuffer[i].z;
                g_quadTextureIndBuffer[i].z = g_quadTextureIndBuffer[i].w;
                g_quadTextureIndBuffer[i].w = tempValue;
            }

            g_quadNormalIndBuffer = (Int4[])m_quadMesh0.QuadNormalInd.Clone();
            for (int i = 0; i < g_quadNormalIndBuffer.Length; i++)
            {
                int tempValue = g_quadNormalIndBuffer[i].z;
                g_quadNormalIndBuffer[i].z = g_quadNormalIndBuffer[i].w;
                g_quadNormalIndBuffer[i].w = tempValue;
            }


            g_quadAdjacentVerticesBuffer = new int[nQuads * 20];
            for (int i = 0; i < g_quadAdjacentVerticesBuffer.Length; i++)
            {
                g_quadAdjacentVerticesBuffer[i] = 0;
            }
            for (int i = 0; i < m_quadMesh0.Quads.Length; i++)
            {
                FillQuadAdjacentVerticesBuffer(i);
            }
            


            // 3. prepare finel data structures
            m_finalVertices = new List<VertexPositionNormalTexture>();
            m_finalIndices = new List<uint>();

        }

        private void FillQuadAdjacentVerticesBuffer(int p_quadIndex)
        {
            int v1Ind = g_quadBuffer[p_quadIndex].x;
            int v2Ind = g_quadBuffer[p_quadIndex].y;
            int v3Ind = g_quadBuffer[p_quadIndex].z;
            int v4Ind = g_quadBuffer[p_quadIndex].w;

            uint val1 = g_vertValenceBuffer[v1Ind];
            uint val2 = g_vertValenceBuffer[v2Ind];
            uint val3 = g_vertValenceBuffer[v3Ind];
            uint val4 = g_vertValenceBuffer[v4Ind];

            {
                // v1:
                int neighbour1Quad = FindQuadIndex(v3Ind, v1Ind, p_quadIndex);
                int v11Ind = GetPreviousVertex(neighbour1Quad, v1Ind);
                g_quadAdjacentVerticesBuffer[20 * p_quadIndex] = v11Ind;

                if (val1 >= 4)
                {
                    int neighbour2Quad = FindQuadIndex(v1Ind, v11Ind, neighbour1Quad);
                    int v12Ind = -1;
                    int v13Ind = -1;
                    GetNextTwoVertexIndices(neighbour2Quad, v11Ind, out v12Ind, out v13Ind);
                    g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 1] = v12Ind;
                    g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 2] = v13Ind;

                    if (val1 >= 5)
                    {
                        int neighbour3Quad = FindQuadIndex(v1Ind, v13Ind, neighbour2Quad);
                        int v14Ind = -1;
                        int v15Ind = -1;
                        GetNextTwoVertexIndices(neighbour3Quad, v13Ind, out v14Ind, out v15Ind);
                        g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 3] = v14Ind;
                        g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 4] = v15Ind;
                    }
                }
            }

            {
                // v2:
                int neighbour1Quad = FindQuadIndex(v1Ind, v2Ind, p_quadIndex);
                int v21Ind = GetPreviousVertex(neighbour1Quad, v2Ind);
                g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 5] = v21Ind;

                if (val2 >= 4)
                {
                    int neighbour2Quad = FindQuadIndex(v2Ind, v21Ind, neighbour1Quad);
                    int v22Ind = -1;
                    int v23Ind = -1;
                    GetNextTwoVertexIndices(neighbour2Quad, v21Ind, out v22Ind, out v23Ind);
                    g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 6] = v22Ind;
                    g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 7] = v23Ind;

                    if (val2 >= 5)
                    {
                        int neighbour3Quad = FindQuadIndex(v2Ind, v23Ind, neighbour2Quad);
                        int v24Ind = -1;
                        int v25Ind = -1;
                        GetNextTwoVertexIndices(neighbour3Quad, v23Ind, out v24Ind, out v25Ind);
                        g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 8] = v24Ind;
                        g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 9] = v25Ind;
                    }
                }
            }

            {
                // v3:
                int neighbour1Quad = FindQuadIndex(v4Ind, v3Ind, p_quadIndex);
                int v31Ind = GetPreviousVertex(neighbour1Quad, v3Ind);
                g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 10] = v31Ind;

                if (val3 >= 4)
                {
                    int neighbour2Quad = FindQuadIndex(v3Ind, v31Ind, neighbour1Quad);
                    int v32Ind = -1;
                    int v33Ind = -1;
                    GetNextTwoVertexIndices(neighbour2Quad, v31Ind, out v32Ind, out v33Ind);
                    g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 11] = v32Ind;
                    g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 12] = v33Ind;

                    if (val3 >= 5)
                    {
                        int neighbour3Quad = FindQuadIndex(v3Ind, v33Ind, neighbour2Quad);
                        int v34Ind = -1;
                        int v35Ind = -1;
                        GetNextTwoVertexIndices(neighbour3Quad, v33Ind, out v34Ind, out v35Ind);
                        g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 13] = v34Ind;
                        g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 14] = v35Ind;
                    }
                }
            }

            {
                // v4:
                int neighbour1Quad = FindQuadIndex(v2Ind, v4Ind, p_quadIndex);
                int v41Ind = GetPreviousVertex(neighbour1Quad, v4Ind);
                g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 15] = v41Ind;

                if (val4 >= 4)
                {
                    int neighbour2Quad = FindQuadIndex(v4Ind, v41Ind, neighbour1Quad);
                    int v42Ind = -1;
                    int v43Ind = -1;
                    GetNextTwoVertexIndices(neighbour2Quad, v41Ind, out v42Ind, out v43Ind);
                    g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 16] = v42Ind;
                    g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 17] = v43Ind;

                    if (val4 >= 5)
                    {
                        int neighbour3Quad = FindQuadIndex(v4Ind, v43Ind, neighbour2Quad);
                        int v44Ind = -1;
                        int v45Ind = -1;
                        GetNextTwoVertexIndices(neighbour3Quad, v43Ind, out v44Ind, out v45Ind);
                        g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 18] = v44Ind;
                        g_quadAdjacentVerticesBuffer[20 * p_quadIndex + 19] = v45Ind;
                    }
                }
            }

        }

        private void FinalizeTriangleMesh()
        {
            m_triangleMesh = new DXTriangleMesh(m_device, m_finalVertices.ToArray(), m_finalIndices.ToArray());
        }


        int FindQuadIndex(int p_vert0, int p_vert1, int p_originalQuad)
        {
            int candidateQuad = -1;
            for (int i = 0; i < m_quadMesh0.Quads.Length; i++)
            {
                if (i == p_originalQuad)
                    continue;

                if ((g_quadBuffer[i].x == p_vert1 && g_quadBuffer[i].y == p_vert0) ||
                    (g_quadBuffer[i].y == p_vert1 && g_quadBuffer[i].x == p_vert0) ||

                    (g_quadBuffer[i].y == p_vert1 && g_quadBuffer[i].w == p_vert0) ||
                    (g_quadBuffer[i].w == p_vert1 && g_quadBuffer[i].y == p_vert0) ||

                    (g_quadBuffer[i].w == p_vert1 && g_quadBuffer[i].z == p_vert0) ||
                    (g_quadBuffer[i].z == p_vert1 && g_quadBuffer[i].w == p_vert0) ||

                    (g_quadBuffer[i].z == p_vert1 && g_quadBuffer[i].x == p_vert0) ||
                    (g_quadBuffer[i].x == p_vert1 && g_quadBuffer[i].z == p_vert0))
                {
                    if (candidateQuad != -1)
                    {
                        // it is a double check here, if an edge is found more than one times (except the p_originalQuad), it is a degenerate mesh
                        Debug.Assert(false);
//                        exit(-1);
                    }

                    candidateQuad = i;
                }
            }

            return candidateQuad;
        }

        // get previous according to the CW (CounteWise) order
        int GetPreviousVertex(int p_quadIndex, int p_vertIndex)
        {
            Int4 quad = g_quadBuffer[p_quadIndex];
            if (p_vertIndex == quad.x)	
            {
                return quad.z;
            }
            else if (p_vertIndex == quad.y)
            {
                return quad.x;
            }
            else if (p_vertIndex == quad.z)
            {
                return quad.w;
            }
            else if (p_vertIndex == quad.w)
            {
                return quad.y;
            }
            return -1;
        }

        void GetNextTwoVertexIndices(int p_quadIndex, int p_vertIndex, out int p_out1, out int p_out2)
        {
            Int4 quad = g_quadBuffer[p_quadIndex];
            if (p_vertIndex == quad.x)
            {
                p_out1 = quad.y;
                p_out2 = quad.w;
                return;
            }
            else if (p_vertIndex == quad.y)
            {
                p_out1 = quad.w;
                p_out2 = quad.z;
                return;
            }
            else if (p_vertIndex == quad.z)
            {
                p_out1 = quad.x;
                p_out2 = quad.y;
                return;
            }
            else if (p_vertIndex == quad.w)
            {
                p_out1 = quad.z;
                p_out2 = quad.x;
                return;
            }
            p_out1 = -1;
            p_out2 = -1;
        }





        byte FindValence(int p_vert)
        {
            byte valence = 0;
            for (int i = 0; i < m_quadMesh0.QuadCount; i++)
            {
                if (m_quadMesh0.Quads[i].vertInd0 == p_vert || m_quadMesh0.Quads[i].vertInd1 == p_vert ||
                    m_quadMesh0.Quads[i].vertInd2 == p_vert || m_quadMesh0.Quads[i].vertInd3 == p_vert)
                    valence++;
            }
            return valence;
        }

        private void RestartStrip()
        {
            int nTriangles = m_triangleStrip.Count - 2;   // the first order is CW, but the second is CCW, third is CW, etc.
            for (int i = 0; i < nTriangles; i++)
            {
                uint firstIndex = (uint)m_finalVertices.Count;
                m_finalVertices.Add(m_triangleStrip[i]);
                m_finalVertices.Add(m_triangleStrip[i + 1]);
                m_finalVertices.Add(m_triangleStrip[i + 2]);

                if (i % 2 == 0)
                {
                    m_finalIndices.Add(firstIndex);
                    m_finalIndices.Add(firstIndex + 1);
                    m_finalIndices.Add(firstIndex + 2);
                }
                else
                {
                    m_finalIndices.Add(firstIndex + 1);
                    m_finalIndices.Add(firstIndex);
                    m_finalIndices.Add(firstIndex + 2);
                }
            }
        }
      

     


    }
}
